/*
 * Redis IO 模型的封装
 * redis采用的是事件驱动机制，内部封装了sunos的evport、linux下的epoll、kqueue和select，通过宏定义判定使用哪个模型。
 * redis在每个模型中分别定义了一个aeApiState结构体，用于记录需要用到的数据，
 *
 * */


#include <sys/epoll.h>

typedef struct aeApiState {
    /*保存epoll实例的描述符。*/
    int epfd;
    /*事件表：保存epoll监控的文件描述符发生的io事件，是一个数组，用于epoll_wait。*/
    struct epoll_event *events;
} aeApiState;

/* 功能：创建epoll接口实例
 * 返回值：0成功 -1失败
 * 参数：aeEventLoop外部循环结构体
 */
static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));

    if (!state) return -1;
    //根据设置的最大监控文件描述符数量，创建epoll_event结构体数组
    state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
    if (!state->events) {
        zfree(state);
        return -1;
    }
    /*调用linux原生api创建epoll*/
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    if (state->epfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    //保存epoll相关信息到主循环的结构体中
    eventLoop->apidata = state;
    return 0;
}
/*调整事件表的大小
 * */
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
    aeApiState *state = eventLoop->apidata;

    state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize);
    return 0;
}
/* 功能：释放epoll实例和事件表空间
 * 返回值：0成功 -1失败
 * 参数：eventLoop 外部循环结构体
 *       fd 要监控的文件描述符
 *       mask 要监控的AE事件
 */
static void aeApiFree(aeEventLoop *eventLoop) {
    aeApiState *state = eventLoop->apidata;

    close(state->epfd);
    zfree(state->events);
    zfree(state);
}
/*在epfd文件描述符标识的事件表上注册fd事件
 * */
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    //如果之前有监控这个文件描述符的事件，就是修改操作，否则是添加操作
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;
    ee.events = 0;
    //如果是修改事件，合并之前的事件类型
    mask |= eventLoop->events[fd].mask;
    //根据mask映射epoll的事件类型
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    //设置事件所对应的目标文件描述符
    ee.data.fd = fd;
    //调用epoll接口，将ee事件注册到epoll中
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

/* 功能：删除监控的事件
 * 返回值：无
 * 参数：eventLoop 外部循环结构体
 *      fd 要删除事件的文件描述符
 *      delmask 要删除的AE事件
 */
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    /*位运算去掉要删除的事件*/
    int mask = eventLoop->events[fd].mask & (~delmask);
    /*翻译成epoll事件*/
    ee.events = 0;
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    /*如果这个描述符还有监控的事件就进行修改操作，否则就删除*/
    if (mask != AE_NONE) {
        epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
    } else {
        /* Note, Kernel < 2.6.9 requires a non null event pointer even for
         * EPOLL_CTL_DEL. */
        epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
    }
}

/*等待所监听的文件描述符上有事件发生
 * 返回值：触发的事件数
 *参数：eventLoop外部循环结构
 *     tvp时间结构体
 */
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;
    //调用epoll_wait监听事件表上是否有事件发生，tvp设置超时时间，没有就设置-1，永久阻塞
    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    //如果至少有一个就绪的事件
    if (retval > 0) {
        int j;

        numevents = retval;
        //遍历就绪的事件表，将其加入到eventLoop的就绪事件表
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;
            //翻译就绪的epoll事件为统一的AE事件
            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
            //添加到就绪的事件表中
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    //返回就绪的事件个数
    return numevents;
}
/*返回正在使用的IO多路复用库的名字*/
static char *aeApiName(void) {
    return "epoll";
}
